//参考
//https://www.runoob.com/cprogramming/c-arrays.html
//https://blog.csdn.net/qq_15144867/article/details/93341737
//https://blog.csdn.net/zqixiao_09/article/details/50877383

#include "ls.h"

#define MASKS_LEN 10
#define SEP "\t"
#define BUFFSIZE 4096

//#define ORIGINAL_DESTINATION "/home/lihuantong/tmp"
#define ORIGINAL_DESTINATION "/tmp"

//视情况采用stat或lstat
int (*stat_f)(const char *, struct stat *) = stat;

const char ERR_CHAR = ' ';

char fileType(mode_t mode)
{
	switch (mode & S_IFMT)
	{
	case S_IFREG:
		return '-';
	case S_IFDIR:
		return 'd';
	case S_IFCHR:
		return 'c';
	case S_IFBLK:
		return 'b';
	case S_IFIFO:
		return 'f';
	case S_IFLNK:
		return 'l';
	case S_IFSOCK:
		return 's';
	default:
		//unknown
		return ERR_CHAR;
	}
}

char filePermission(mode_t mode, mode_t mask)
{
	if (mode & mask == 0)
		return '-';
	else
	{
		switch (mask)
		{
		case S_IRUSR:
		case S_IRGRP:
		case S_IROTH:
			return 'r';
		case S_IWUSR:
		case S_IWGRP:
		case S_IWOTH:
			return 'w';
		case S_IXUSR:
		case S_IXGRP:
		case S_IXOTH:
			return 'x';
		default:
			return ERR_CHAR;
		}
	}
}

const size_t MODE_STR_LEN = 12;
const mode_t MASKS[MASKS_LEN] = {0,
								 //用户（所有者）
								 S_IRUSR, S_IWUSR, S_IXUSR,
								 //组
								 S_IRGRP, S_IWGRP, S_IXGRP,
								 //其他
								 S_IROTH, S_IWOTH, S_IXOTH};
const char PERMISSION_STR[] = "Erwxrwxrwx";
void filePermission2(char *modeStr, mode_t mode, size_t idx);
inline void filePermission2(char *modeStr, mode_t mode, size_t idx)
{
	modeStr[idx] = (mode & MASKS[idx]) ? PERMISSION_STR[idx] : '-';
}
void outputFileMode(mode_t mode)
{
	char modeStr[MODE_STR_LEN] /*  = {'\0'} */;
	modeStr[0] = fileType(mode);
	size_t i;
	for (i = 1; i < MASKS_LEN; ++i)
	{
		//modeStr[i] = filePermission(mode, MASKS[i]);
		filePermission2(modeStr, mode, i);
	}
	modeStr[MASKS_LEN] = '\0';
	printf("%s" SEP, modeStr);
}

void print_type(mode_t mode)
{
	printf("%c", fileType(mode));
}
void print_perm(mode_t mode)
{
	char modeStr[MODE_STR_LEN];
	size_t i;
	for (i = 1; i < MASKS_LEN; ++i)
		filePermission2(modeStr, mode, i);
	modeStr[MASKS_LEN] = '\0';
	printf("%s" SEP, modeStr + 1);
}

void print_link(unsigned long link)
{
	printf("%lu" SEP, link);
}

void print_size(long size)
{
	printf("%ld" SEP, size);
}

void print_time(time_t t)
{
	char *s = ctime(&t);
	//abort();
	char *s2 = strchr(s, '\n');
	if (s2 != NULL)
		*s2 = '\0';
	printf("%s" SEP, s);
}

void print_filename(const struct dirent *curdp)
{
	printf("%s\n", curdp->d_name);
}

void print_usrname(uid_t uid)
{
	printf("%s" SEP, getpwuid(uid)->pw_name);
}

void print_grname(gid_t gid)
{
	printf("%s" SEP, getgrgid(gid)->gr_name);
}

//https://www.cnblogs.com/xiaofeiIDO/p/6695459.html
const mode_t MODE = S_IRWXU | S_IRWXG | S_IRWXO;
size_t rel_start = 0;
void createDirInDst(const char *path0)
{
	char path[PATH_MAX] = {0};
	strcpy(path, getenv("HOME"));
	strcat(path, ORIGINAL_DESTINATION);
	strcat(path, path0 + rel_start);
	if (access(path, F_OK) != 0)
	{
		if (mkdir(path, MODE) != 0)
			perror(path);
	}
}

pthread_mutex_t mtx;
unsigned waiting_cnt = 0;

void waiting_cnt_add()
{
	pthread_mutex_lock(&mtx);
	waiting_cnt++;
	pthread_mutex_unlock(&mtx);
}
void waiting_cnt_minus()
{
	pthread_mutex_lock(&mtx);
	waiting_cnt--;
	pthread_mutex_unlock(&mtx);
}

//文件类型 权限 硬链接数 所有者用户名 所有者所在组用户名 文件大小 最后修改时间
void *ls(void *arg)
{
	char dirPath[PATH_MAX];
	memset(dirPath, '\0', sizeof(dirPath));
	strcpy(dirPath, (char *)arg);
	DIR *catalog = opendir(dirPath);
	if (catalog == NULL)
	{
		printf("open directory failed\n");
		perror(dirPath);
	}
	else
	{
		//目录项
		struct dirent *currentdp;
		for (currentdp = readdir(catalog); currentdp != NULL; currentdp =
																  readdir(catalog))
		{
			if ((strcmp(currentdp->d_name, ".") != 0) && (strcmp(currentdp->d_name, "..") != 0))
			{
				struct stat currentstat;
				char *dirPath_copy = (char *)malloc(PATH_MAX * sizeof(char));
				memset(dirPath_copy, '\0', sizeof(dirPath_copy));
				strcpy(dirPath_copy, dirPath);
				strcat(strcat(dirPath_copy, "/"), currentdp->d_name);
				if (stat_f(dirPath_copy, &currentstat) != 0)
				{
					//出错
					printf("get stat error\n");
					perror(dirPath_copy);
					free(dirPath_copy);
					//无需释放
					//free(currentdp);
					//break;
					continue;
				}
				else if (fileType(currentstat.st_mode) != 'd')
				{
					//						pid_t pid_child, pid_return;
					//						pid_child = fork();
					//						if (pid_child < 0)
					//							printf("Error occured on forking.\n");
					//						else if (pid_child == 0) {
					//							execl("./mycp", "./mycp", dirPath_copy,
					//									"/home/lihuantong/tmp", NULL);
					//						}
					//						do {
					//							pid_return = waitpid(pid_child, NULL, WNOHANG);
					//						} while (pid_return == 0);
					//						if (pid_return != pid_child) {
					//							printf("1some error occured\n");
					//						}
					pthread_t tid;
					waiting_cnt_add();
					pthread_create(&tid, NULL, /* (void *) */ cp, dirPath_copy);
					//参考
					//https://blog.csdn.net/Savage888777/article/details/106037411
					//https://blog.csdn.net/Rong_Toa/article/details/111241688
					//pthread_join(tid, NULL);
					//free(dirPath_copy);
					pthread_detach(tid);
				}
				else if (fileType(currentstat.st_mode) == 'd')
				{
					//						pid_t pid_child, pid_return;
					//						pid_child = fork();
					//						if (pid_child < 0)
					//							printf("Error occured on forking.\n");
					//						else if (pid_child == 0) {
					//							execl("./myls", "./myls", dirPath_copy, NULL);
					//						}
					//						do {
					//							pid_return = waitpid(pid_child, NULL, WNOHANG);
					//						} while (pid_return == 0);
					//						if (pid_return != pid_child) {
					//							printf("2some error occured\n");
					//						}
					createDirInDst(dirPath_copy);
					pthread_t tid;
					waiting_cnt_add();
					pthread_create(&tid, NULL, /* (void *) */ ls, dirPath_copy);
					//pthread_join(tid, NULL);
					//free(dirPath_copy);
					pthread_detach(tid);
				}
				//free(dirPath_copy);
			}
		}
	}
	closedir(catalog);
	free(arg);
	waiting_cnt_minus();
}

char *concat(char A[], char B[])
{
	char *p = A;
	char *q = B;
	q = strrchr(q, '/');
	while (*p)
		p++;
	*p = '/';
	p++;
	q = q == NULL ? B : (q + 1);
	while (*q)
		*p++ = *q++;
	*p = '\0';
	return A;
}

//https://blog.csdn.net/xhhjin/article/details/6369336
bool fileExist(const char *path)
{
	FILE *f = fopen(path, "rb");
	if (f == NULL)
		return false;
	else
	{
		fclose(f);
		return true;
	}
}

pthread_mutex_t stdio_mtx;
int openExistingFile(const char *desPath)
{
	char command;
	int desfd=0;
	pthread_mutex_lock(&stdio_mtx);
	printf("%s already exists, will you replace or add?[r/a]\n", desPath);
	do
	{
		command = getchar();
	} while (command != 'r' && command != 'a');
	pthread_mutex_unlock(&stdio_mtx);
	if (command == 'r')
	{
		desfd = open(desPath, O_TRUNC | O_RDWR, MODE);
	}
	else if (command == 'a')
	{
		desfd = open(desPath, O_APPEND | O_RDWR, MODE);
	}
	return desfd;
}

void *cp(void *arg)
{
	//printf("%s\n",arg);
	char src[PATH_MAX];
	char des[PATH_MAX] = {0} /* ORIGINAL_DESTINATION */;
	//根据环境变量确定目标目录
	strcpy(des, getenv("HOME"));
	strcat(des, ORIGINAL_DESTINATION);
	memset(src, '\0', sizeof(src));
	strcpy(src, (char *)arg);
	int srcfd = open(src, O_RDONLY);
	if (errno != 0)
	{
		perror(src);
		close(srcfd);
	}
	size_t desPathCap = strlen(src) + strlen(des) + 2;
	char *desPath = malloc(desPathCap);
	memset(desPath, 0, desPathCap);
	strcpy(desPath, des);
	//concat(desPath, src);
	strcat(desPath, src + rel_start);
	/* printf("%d\n",access(desPath,F_OK));
	if(errno!=0)perror("access"); */
	/* printf("%s\n",fileExist(desPath)?"file exist":"file not exist");
	if(errno!=0)perror("fopen"); */
	int desfd = open(desPath, O_CREAT | O_APPEND | O_WRONLY | O_EXCL, MODE);
	if (errno == 20) //不是目录是文件路径
	{
		desfd = open(des, O_CREAT | O_APPEND | O_RDWR | O_EXCL, MODE);
		if (errno == 17)
		{
			desfd=openExistingFile(desPath);
		}
	}
	else if (errno == 17) //文件已存在
	{
		desfd=openExistingFile(desPath);
	}
	else if (desfd == -1)
	{
		perror(desPath);
	}
	char buff[BUFFSIZE];
	ssize_t nbytes_out, nbytes_in;
	if (desfd != -1 && srcfd != -1)
	{
		while ((nbytes_out = read(srcfd, buff, BUFFSIZE)) > 0)
		{
			nbytes_in = write(desfd, buff, nbytes_out);
			if (nbytes_in != nbytes_out)
			{
				perror("write error");
				break;
			}
		}
		if ((nbytes_out < 0) & (errno != 0))
			perror(src);
	}
	close(srcfd);
	close(desfd);
	//printf("%s\n",desPath);
	free(desPath);
	free(arg);
	waiting_cnt_minus();
	return 0;
}
